
// these are all the viewer dialogs with graphical panel areas
// they can be instantiated multiple times

#include "wxvbam.h"
#include "viewsupt.h"
#include <wx/colordlg.h>
#include <wx/ffile.h>

// FIXME: many of these read e.g. palette data directly without regard to
// byte order.  Need to determine where things are stored in emulated machine
// order and where in native order, and swap the latter on big-endian

// most of these have label fields that need to be sized and later filled
// mv is a string to initialize with for sizing
#define getlab(v, n, mv) do { \
    v = XRCCTRL(*this, n, wxControl); \
    if(!v) \
	baddialog(); \
    v->SetLabel(wxT(mv)); \
} while(0)

// FIXME: this should be in a header
extern u8 gbInvertTab[256];

// avoid exporting classes
namespace Viewers {
    class MapViewer : public GfxViewer
    {
    public:
	MapViewer() : GfxViewer(wxT("MapViewer"), 1024, 1024)
	{
	    frame = bg = 0;
	    getradio(fr0 = , "Frame0", frame, 0);
	    getradio(fr1 = , "Frame1", frame, 0xa000);
	    getradio(bg0 = , "BG0", bg, 0);
	    getradio(bg1 = , "BG1", bg, 1);
	    getradio(bg2 = , "BG2", bg, 2);
	    getradio(bg3 = , "BG3", bg, 3);
	    getlab(modelab, "Mode", "8");
	    getlab(mapbase, "MapBase", "0xWWWWWWWW");
	    getlab(charbase, "CharBase", "0xWWWWWWWW");
	    getlab(size, "Size", "1024x1024");
	    getlab(colors, "Colors", "2WW");
	    getlab(prio, "Priority", "3");
	    getlab(mosaic, "Mosaic", "0");
	    getlab(overflow, "Overflow", "0");
	    getlab(coords, "Coords", "(1023,1023)");
	    getlab(addr, "Address", "0xWWWWWWWW");
	    getlab(tile, "Tile", "1023");
	    getlab(flip, "Flip", "HV");
	    getlab(palette, "Palette", "---");
	    Fit();
	    selx = sely = -1;
	    Update();
	}
	void Update()
	{
	    mode = DISPCNT & 7;
	    switch(bg) {
	    case 0:
		control = BG0CNT;
		break;
	    case 1:
		control = BG1CNT;
		break;
	    case 2:
		control = BG2CNT;
		break;
	    case 3:
		control = BG3CNT;
		break;
	    }
	    bool fr0en = true, fr1en = true, bg0en = true, bg1en = true,
		 bg2en = true, bg3en = true;
	    switch(mode) {
	    case 0:
		fr0en = fr1en = false;
		renderTextScreen();
		break;
	    case 1:
		fr0en = fr1en = false;
		bg3en = false;
		if(bg == 3) {
		    bg = 0;
		    control = BG0CNT;
		    bg0->SetValue(true);
		}
		if(bg < 2)
		    renderTextScreen();
		else
		    renderRotScreen();
		break;
	    case 2:
		fr0en = fr1en = false;
		bg0en = bg1en = false;
		if(bg < 2) {
		    bg = 2;
		    control = BG2CNT;
		    bg2->SetValue(true);
		}
		renderRotScreen();
		break;
	    case 3:
		fr0en = fr1en = false;
		bg0en = bg1en = bg2en = bg3en = false;
		bg = 2;
		bg2->SetValue(true);
		renderMode3();
		break;
	    case 4:
		bg0en = bg1en = bg2en = bg3en = false;
		bg = 2;
		bg2->SetValue(true);
		renderMode4();
		break;
	    case 5:
	    case 6:
	    case 7:
		bg = 2;
		bg2->SetValue(true);
		renderMode5();
		break;
	    }
	    ChangeBMP();
	    fr0->Enable(fr0en);
	    fr1->Enable(fr1en);
	    bg0->Enable(bg0en);
	    bg1->Enable(bg1en);
	    bg2->Enable(bg2en);
	    bg3->Enable(bg3en);

	    wxString s;
	    s.Printf(wxT("%d"), (int)mode);
	    modelab->SetLabel(s);
	    if(mode >= 3) {
		mapbase->SetLabel(wxEmptyString);
		charbase->SetLabel(wxEmptyString);
	    } else {
		s.Printf(wxT("0x%08X"), ((control >> 8) & 0x1f) *  0x800 + 0x6000000);
		mapbase->SetLabel(s);
		s.Printf(wxT("0x%08X"), ((control >> 2) & 0x03) * 0x4000 + 0x6000000);
		charbase->SetLabel(s);
	    }
	    s.Printf(wxT("%dx%d"), gv->bmw, gv->bmh);
	    size->SetLabel(s);
	    colors->SetLabel(control & 0x80 ? wxT("256") : wxT("16"));
	    s.Printf(wxT("%d"), control & 3);
	    prio->SetLabel(s);
	    mosaic->SetLabel(control & 0x40 ? wxT("1") : wxT("0"));
	    overflow->SetLabel(bg <= 1 ? wxEmptyString :
			       control & 0x2000 ? wxT("1") : wxT("0"));
	    UpdateMouseInfo();
	}

	void UpdateMouseInfoEv(wxMouseEvent &ev)
	{
	    selx = ev.GetX();
	    sely = ev.GetY();
	    UpdateMouseInfo();  // note that this will be inaccurate if game
	                        // not paused since last refresh
	}

	u32 AddressFromSel()
	{
	    u32 base = ((control >> 8) & 0x1f) * 0x800 + 0x6000000;
	    // all text bgs (16 bits)
	    if(mode == 0 || (mode < 3 && bg < 2) || mode == 6 || mode == 7) {
		if(sely > 255) {
		    base += 0x800;
		    if(gv->bmw > 256)
			base += 0x800;
		}
		if(selx >= 256)
		    base += 0x800;
		return base + ((selx & 0xff) >> 3) * 2 + 64 * ((sely & 0xff) >> 3);
	    }
	    // rot bgs (8 bits)
	    if(mode < 3)
		return base + (selx>>3) + (gv->bmw>>3) * (sely >> 3);
	    // mode 3/5 (16 bits)
	    if(mode != 4)
		return 0x6000000 + 0xa000*frame + (selx + gv->bmw * sely) * 2;
	    // mode 4 (8 bits)
	    return 0x6000000 + 0xa000*frame + selx + gv->bmw * sely;
	}

	void UpdateMouseInfo()
	{
	    if(selx > gv->bmw || sely > gv->bmh)
		selx = sely = -1;
	    if(selx < 0) {
		coords->SetLabel(wxEmptyString);
		addr->SetLabel(wxEmptyString);
		tile->SetLabel(wxEmptyString);
		flip->SetLabel(wxEmptyString);
		palette->SetLabel(wxEmptyString);
	    } else {
		wxString s;
		s.Printf(wxT("(%d,%d)"), selx, sely);
		coords->SetLabel(s);
		u32 address = AddressFromSel();
		s.Printf(wxT("0x%08X"), address);
		addr->SetLabel(s);

		if(!mode || (mode < 3 || mode > 5) && bg < 2) {
		    u16 value = *((u16 *)&vram[address - 0x6000000]);
		    s.Printf(wxT("%d"), value & 1023);
		    tile->SetLabel(s);
		    s = value & 1024 ? wxT('H') : wxT('-');
		    s += value & 2048 ? wxT('V') : wxT('-');
		    flip->SetLabel(s);
		    if(control & 0x80)
			palette->SetLabel(wxT("---"));
		    else {
			s.Printf(wxT("%d"), (value >> 12) & 15);
			palette->SetLabel(s);
		    }
		} else {
		    tile->SetLabel(wxT("---"));
		    flip->SetLabel(wxT("--"));
		    palette->SetLabel(wxT("---"));
		}
	    }
	}
    protected:
	u16 control, mode;
	int frame, bg;
	wxRadioButton *fr0, *fr1, *bg0, *bg1, *bg2, *bg3;
	wxControl *modelab, *mapbase, *charbase, *size, *colors, *prio, *mosaic,
	    *overflow;
	wxControl *coords, *addr, *tile, *flip, *palette;
	int selx, sely;

	// following routines were copied from win32/MapView.cpp with little
	// attempt to read & validate, except:
	//    stride = 1024, rgb instead of bgr
	// FIXME: probably needs changing for big-endian

	void renderTextScreen()
	{
	    u16 *palette = (u16 *)paletteRAM;
	    u8 *charBase = &vram[((control >> 2) & 0x03) * 0x4000];
	    u16 *screenBase = (u16 *)&vram[((control >> 8) & 0x1f) * 0x800];
	    u8 *bmp = image.GetData();

	    int sizeX = 256;
	    int sizeY = 256;
	    switch((control >> 14) & 3) {
	    case 0:
		break;
	    case 1:
		sizeX = 512;
		break;
	    case 2:
		sizeY = 512;
		break;
	    case 3:
		sizeX = 512;
		sizeY = 512;
		break;
	    }

	    BMPSize(sizeX, sizeY);

	    if(control & 0x80) {
		for(int y = 0; y < sizeY; y++) {
		    int yy = y & 255;

		    if(y == 256 && sizeY > 256) {
			screenBase += 0x400;
			if(sizeX > 256)
			    screenBase += 0x400;
		    }
		    u16 *screenSource = screenBase + ((yy>>3)*32);

		    for(int x = 0; x < sizeX; x++) {
			u16 data = *screenSource;

			int tile = data & 0x3FF;
			int tileX = (x & 7);
			int tileY = y & 7;

			if(data & 0x0400)
			    tileX = 7 - tileX;
			if(data & 0x0800)
			    tileY = 7 - tileY;

			u8 c = charBase[tile * 64 + tileY * 8 + tileX];

			u16 color = palette[c];

			*bmp++ = (color & 0x1f) << 3;
			*bmp++ = ((color >> 5) & 0x1f) << 3;
			*bmp++ = ((color >> 10) & 0x1f) << 3;

			if(data & 0x0400) {
			    if(tileX == 0)
				screenSource++;
			} else if(tileX == 7)
				screenSource++;
			if(x == 255 && sizeX > 256) {
			    screenSource = screenBase + 0x400 + ((yy>>3)*32);
			}
		    }
		    bmp += 3 * (1024 - sizeX);
		}
	    } else {
		for(int y = 0; y < sizeY; y++) {
		    int yy = y & 255;

		    if(y == 256 && sizeY > 256) {
			screenBase += 0x400;
			if(sizeX > 256)
			    screenBase += 0x400;
		    }
		    u16 *screenSource = screenBase + ((yy>>3)*32);

		    for(int x = 0; x < sizeX; x++) {
			u16 data = *screenSource;

			int tile = data & 0x3FF;
			int tileX = (x & 7);
			int tileY = y & 7;

			if(data & 0x0400)
			    tileX = 7 - tileX;
			if(data & 0x0800)
			    tileY = 7 - tileY;

			u8 color = charBase[tile * 32 + tileY * 4 + (tileX>>1)];

			if(tileX & 1) {
			    color = (color >> 4);
			} else {
			    color &= 0x0F;
			}

			int pal = (*screenSource>>8) & 0xF0;
			u16 color2 = palette[pal + color];

			*bmp++ = (color2 & 0x1f) << 3;
			*bmp++ = ((color2 >> 5) & 0x1f) << 3;
			*bmp++ = ((color2 >> 10) & 0x1f) << 3;

			if(data & 0x0400) {
			    if(tileX == 0)
				screenSource++;
			} else if(tileX == 7)
				screenSource++;

			if(x == 255 && sizeX > 256) {
			    screenSource = screenBase + 0x400 + ((yy>>3)*32);
			}
		    }
		    bmp += 3 * (1024 - sizeX);
		}
	    }
#if 0
	    switch(bg) {
	    case 0:
		renderView(BG0HOFS<<8, BG0VOFS<<8,
			   0x100, 0x000,
			   0x000, 0x100,
			   (sizeX -1) <<8,
			   (sizeY -1) << 8,
			   true);
		break;
	    case 1:
		renderView(BG1HOFS<<8, BG1VOFS<<8,
			   0x100, 0x000,
			   0x000, 0x100,
			   (sizeX -1) <<8,
			   (sizeY -1) << 8,
			   true);
		break;
	    case 2:
		renderView(BG2HOFS<<8, BG2VOFS<<8,
			   0x100, 0x000,
			   0x000, 0x100,
			   (sizeX -1) <<8,
			   (sizeY -1) << 8,
			   true);
		break;
	    case 3:
		renderView(BG3HOFS<<8, BG3VOFS<<8,
			   0x100, 0x000,
			   0x000, 0x100,
			   (sizeX -1) <<8,
			   (sizeY -1) << 8,
			   true);
		break;
	    }
#endif
	}

	void renderRotScreen()
	{
	    u16 *palette = (u16 *)paletteRAM;
	    u8 *charBase = &vram[((control >> 2) & 0x03) * 0x4000];
	    u8 *screenBase = (u8 *)&vram[((control >> 8) & 0x1f) * 0x800];
	    u8 *bmp = image.GetData();

	    int sizeX = 128;
	    int sizeY = 128;
	    switch((control >> 14) & 3) {
	    case 0:
		break;
	    case 1:
		sizeX = sizeY = 256;
		break;
	    case 2:
		sizeX = sizeY = 512;
		break;
	    case 3:
		sizeX = sizeY = 1024;
		break;
	    }

	    BMPSize(sizeX, sizeY);

	    if(control & 0x80) {
		for(int y = 0; y < sizeY; y++) {
		    for(int x = 0; x < sizeX; x++) {
			int tile = screenBase[(x>>3) + (y>>3)*(sizeX>>3)];

			int tileX = (x & 7);
			int tileY = y & 7;

			u8 color = charBase[tile * 64 + tileY * 8 + tileX];
			u16 color2 = palette[color];

			*bmp++ = (color2 & 0x1f) << 3;
			*bmp++ = ((color2 >> 5) & 0x1f) << 3;
			*bmp++ = ((color2 >> 10) & 0x1f) << 3;
		    }
		}
		bmp += 3 * (1024 - sizeX);
	    } else {
		for(int y = 0; y < sizeY; y++) {
		    for(int x = 0; x < sizeX; x++) {
			int tile = screenBase[(x>>3) + (y>>3)*(sizeX>>3)];

			int tileX = (x & 7);
			int tileY = y & 7;

			u8 color = charBase[tile * 64 + tileY * 8 + tileX];
			u16 color2 = palette[color];

			*bmp++ = (color2 & 0x1f) << 3;
			*bmp++ = ((color2 >> 5) & 0x1f) << 3;
			*bmp++ = ((color2 >> 10) & 0x1f) << 3;
		    }
		}
		bmp += 3 * (1024 - sizeX);
	    }

	    u32 xx;
	    u32 yy;

	    switch(bg) {
	    case 2:
		xx = BG2X_L | BG2X_H << 16;
		yy = BG2Y_L | BG2Y_H << 16;


#if 0
		renderView(xx, yy,
			   BG2PA, BG2PC,
			   BG2PB, BG2PD,
			   (sizeX -1) <<8,
			   (sizeY -1) << 8,
			   (control & 0x2000) != 0);
#endif
		break;
	    case 3:
		xx = BG3X_L | BG3X_H << 16;
		yy = BG3Y_L | BG3Y_H << 16;
#if 0
		renderView(xx, yy,
			   BG3PA, BG3PC,
			   BG3PB, BG3PD,
			   (sizeX -1) <<8,
			   (sizeY -1) << 8,
			   (control & 0x2000) != 0);
#endif
		break;
	    }
	}

	void renderMode3()
	{
	    u8 *bmp = image.GetData();
	    u16 *src = (u16 *)&vram[0];

	    BMPSize(240, 160);

	    for(int y = 0; y < 160; y++) {
		for(int x = 0; x < 240; x++) {
		    u16 data = *src++;
		    *bmp++ = (data & 0x1f) << 3;
		    *bmp++ = ((data >> 5) & 0x1f) << 3;
		    *bmp++ = ((data >> 10) & 0x1f) << 3;
		}
		bmp += 3 * (1024 - 240);
	    }
	}


	void renderMode4()
	{
	    u8 *bmp = image.GetData();
	    u8 *src = frame ? &vram[0xa000] : &vram[0];
	    u16 *pal = (u16 *)&paletteRAM[0];

	    BMPSize(240, 160);

	    for(int y = 0; y < 160; y++) {
		for(int x = 0; x < 240; x++) {
		    u8 c = *src++;
		    u16 data = pal[c];
		    *bmp++ = (data & 0x1f) << 3;
		    *bmp++ = ((data >> 5) & 0x1f) << 3;
		    *bmp++ = ((data >> 10) & 0x1f) << 3;
		}
		bmp += 3 * (1024 - 240);
	    }
	}


	void renderMode5()
	{
	    u8 *bmp = image.GetData();
	    u16 *src = (u16 *)(frame ? &vram[0xa000] : &vram[0]);

	    BMPSize(160, 128);

	    for(int y = 0; y < 128; y++) {
		for(int x = 0; x < 160; x++) {
		    u16 data = *src++;
		    *bmp++ = (data & 0x1f) << 3;
		    *bmp++ = ((data >> 5) & 0x1f) << 3;
		    *bmp++ = ((data >> 10) & 0x1f) << 3;
		}
		bmp += 3 * (1024 - 160);
	    }
	}

	DECLARE_EVENT_TABLE()
    };

    BEGIN_EVENT_TABLE(MapViewer, GfxViewer)
	EVT_GFX_CLICK(wxID_ANY, MapViewer::UpdateMouseInfoEv)
    END_EVENT_TABLE()
    
    class GBMapViewer : public GfxViewer
    {
    public:
	GBMapViewer() : GfxViewer(wxT("GBMapViewer"), 256, 256)
	{
	    getradio(,"CharBase0", charbase, 0x0000);
	    getradio(,"CharBase1", charbase, 0x0800);
	    getradio(,"MapBase0", mapbase, 0x1800);
	    getradio(,"MapBase1", mapbase, 0x1c00);
	    getlab(coords, "Coords", "(2WW,2WW)");
	    getlab(addr, "Address", "0xWWWW");
	    getlab(tile, "Tile", "2WW");
	    getlab(flip, "Flip", "HV");
	    getlab(palette, "Palette", "---");
	    getlab(prio, "Priority", "P");
	    Fit();
	    selx = sely = -1;
	    Update();
	}
	void Update()
	{
	    u8 *bank0, *bank1;
	    if(gbCgbMode) {
		bank0 = &gbVram[0x0000];
		bank1 = &gbVram[0x2000];
	    } else {
		bank0 = &gbMemory[0x8000];
		bank1 = NULL;
	    }
	    int tile_map_address = mapbase;

	    // following copied almost verbatim from win32/GBMapView.cpp
	    int tile = 0;
	    for(int y = 0; y < 32; y++) {
		for(int x = 0; x < 32; x++) {
		    u8 *bmp = &image.GetData()[y * 8 * 32 * 24 + x*24];
		    u8 attrs = 0;
		    if(bank1 != NULL)
			attrs = bank1[tile_map_address];
		    u8 tile = bank0[tile_map_address];
		    tile_map_address++;

		    if(charbase) {
			if(tile < 128) tile += 128;
			else tile -= 128;
		    }
		    for(int j = 0; j < 8; j++) {
			int charbase_address = attrs & 0x40 ?
			    charbase + tile*16 + (7-j)*2:
			    charbase + tile*16+j*2;

			u8 tile_a = 0;
			u8 tile_b = 0;

			if(attrs & 0x08) {
			    tile_a = bank1[charbase_address++];
			    tile_b = bank1[charbase_address];
			} else {
			    tile_a = bank0[charbase_address++];
			    tile_b = bank0[charbase_address];
			}

			if(attrs & 0x20) {
			    tile_a = gbInvertTab[tile_a];
			    tile_b = gbInvertTab[tile_b];
			}

			u8 mask = 0x80;

			while(mask > 0) {
			    u8 c = (tile_a & mask) ? 1 : 0;
			    c += (tile_b & mask) ? 2 : 0;

			    if(gbCgbMode)
				c = c + (attrs & 7)*4;

			    u16 color = gbPalette[c];

			    *bmp++ = (color & 0x1f) << 3;
			    *bmp++ = ((color >> 5) & 0x1f) << 3;
			    *bmp++ = ((color >> 10) & 0x1f) << 3;

			    mask >>= 1;
			}
			bmp += 31*24;
		    }
		}
	    }
	    ChangeBMP();
	    UpdateMouseInfo();
	}

	void UpdateMouseInfoEv(wxMouseEvent &ev)
	{
	    selx = ev.GetX();
	    sely = ev.GetY();
	    UpdateMouseInfo();  // note that this will be inaccurate if game
	                        // not paused since last refresh
	}

	void UpdateMouseInfo()
	{
	    if(selx > gv->bmw || sely > gv->bmh)
		selx = sely = -1;
	    if(selx < 0) {
		coords->SetLabel(wxEmptyString);
		addr->SetLabel(wxEmptyString);
		tile->SetLabel(wxEmptyString);
		flip->SetLabel(wxEmptyString);
		palette->SetLabel(wxEmptyString);
		prio->SetLabel(wxEmptyString);
	    } else {
		wxString s;
		s.Printf(wxT("(%d,%d)"), selx, sely);
		coords->SetLabel(s);
		u16 address = mapbase + 0x8000 + (sely >> 3) * 32 + (selx >> 3);
		s.Printf(wxT("0x%04X"), address);
		addr->SetLabel(s);
		u8 attrs = 0;
		u8 tilev = gbMemoryMap[9][address & 0xfff];
		if(gbCgbMode) {
		    attrs = gbVram[0x2000 + address - 0x8000];
		    tilev = gbVram[address & 0x1fff];
		}
		if(charbase) {
		    if(tilev >= 128)
			tilev -= 128;
		    else
			tilev += 128;
		}
		s.Printf(wxT("%d"), (int)tilev);
		tile->SetLabel(s);
		s = attrs & 0x20 ? wxT('H') : wxT('-');
		s += attrs & 0x40 ? wxT('V') : wxT('-');
		flip->SetLabel(s);
		if(gbCgbMode) {
		    s.Printf(wxT("%d"), attrs & 7);
		    palette->SetLabel(s);
		} else
		    palette->SetLabel(wxT("---"));
		prio->SetLabel(wxString(attrs & 0x80 ? wxT('P') : wxT('-')));
	    }
	}
    protected:
	int charbase, mapbase;
	wxControl *coords, *addr, *tile, *flip, *palette, *prio;
	int selx, sely;

	DECLARE_EVENT_TABLE()
    };

    BEGIN_EVENT_TABLE(GBMapViewer, GfxViewer)
	EVT_GFX_CLICK(wxID_ANY, GBMapViewer::UpdateMouseInfoEv)
    END_EVENT_TABLE()
}

void MainFrame::MapViewer()
{
    switch(panel->game_type()) {
    case IMAGE_GBA:
	LoadXRCViewer(Map);
	break;
    case IMAGE_GB:
	LoadXRCViewer(GBMap);
	break;
    }
}

namespace Viewers {
    class OAMViewer : public GfxViewer
    {
    public:
	OAMViewer() : GfxViewer(wxT("OAMViewer"), 64, 64)
	{
	    sprite = 0;
	    getspin(,"Sprite", sprite);
	    getlab(pos, "Pos", "5WW,2WW");
	    getlab(mode, "Mode", "3");
	    getlab(colors, "Colors", "256");
	    getlab(pallab, "Palette", "1W");
	    getlab(tile, "Tile", "1WWW");
	    getlab(prio, "Priority", "3");
	    getlab(size, "Size", "64x64");
	    getlab(rot, "Rotation", "3W");
	    getlab(flg, "Flags", "RHVMD");
	    Fit();
	    Update();
	}
	void Update()
	{
	    u16 *sparms = &((u16 *)oam)[4 * sprite];
	    u16 a0 = sparms[0], a1 = sparms[1], a2 = sparms[2];
	    u16 *pal = &((u16 *)paletteRAM)[0x100];
	    u8 *bmp = image.GetData();

	    int sizeX = 8, sizeY = 8;

	    // following is almost verbatim from OamView.cpp
	    // shape = (a0 >> 14) & 3;
	    // size = (a1 >> 14) & 3;
	    switch(((a0 >> 12) & 0xc) | (a1 >> 14)) {
	    case 0:
		break;
	    case 1:
		sizeX = sizeY = 16;
		break;
	    case 2:
		sizeX = sizeY = 32;
		break;
	    case 3:
		sizeX = sizeY = 64;
		break;
	    case 4:
		sizeX = 16;
		break;
	    case 5:
		sizeX = 32;
		break;
	    case 6:
		sizeX = 32;
		sizeY = 16;
		break;
	    case 7:
		sizeX = 64;
		sizeY = 32;
		break;
	    case 8:
		sizeY = 16;
		break;
	    case 9:
		sizeY = 32;
		break;
	    case 10:
		sizeX = 16;
		sizeY = 32;
		break;
	    case 11:
		sizeX = 32;
		sizeY = 64;
		break;
	    default:
		pos->SetLabel(wxEmptyString);
		mode->SetLabel(wxEmptyString);
		colors->SetLabel(wxEmptyString);
		pallab->SetLabel(wxEmptyString);
		tile->SetLabel(wxEmptyString);
		prio->SetLabel(wxEmptyString);
		size->SetLabel(wxEmptyString);
		rot->SetLabel(wxEmptyString);
		flg->SetLabel(wxEmptyString);
		BMPSize(sizeX, sizeY);
		memset(bmp, 0, 8 * 8 * 3);
		ChangeBMP();
		return;
	    }
	    BMPSize(sizeX, sizeY);

	    int sy = (a0 & 255);

	    if(a0 & 0x2000) {
		int c = (a2 & 0x3FF);
		//if((DISPCNT & 7) > 2 && (c < 512))
		//    return;
		int inc = 32;
		if(DISPCNT & 0x40)
		    inc = sizeX >> 2;
		else
		    c &= 0x3FE;

		for(int y = 0; y < sizeY; y++) {
		    for(int x = 0; x < sizeX; x++) {
			u32 color = vram[0x10000 + (((c + (y>>3) * inc)*
						     32 + (y & 7) * 8 + (x >> 3) * 64 +
						     (x & 7))&0x7FFF)];
			color = pal[color];
			*bmp++ = (color & 0x1f) << 3;
			*bmp++ = ((color >> 5) & 0x1f) << 3;
			*bmp++ = ((color >> 10) & 0x1f) << 3;
		    }
		    bmp += (64 - sizeX) * 3;
		}
	    } else {
		int c = (a2 & 0x3FF);
		//if((DISPCNT & 7) > 2 && (c < 512))
		//    return;
		int inc = 32;
		if(DISPCNT & 0x40)
		    inc = sizeX >> 3;
		int palette = (a2 >> 8) & 0xF0;
		for(int y = 0; y < sizeY; y++) {
		    for(int x = 0; x < sizeX; x++) {
			u32 color = vram[0x10000 + (((c + (y>>3) * inc)*
						     32 + (y & 7) * 4 + (x >> 3) * 32 +
						     ((x & 7)>>1))&0x7FFF)];
			if(x & 1)
			    color >>= 4;
			else
			    color &= 0x0F;

			color = pal[palette+color];
			*bmp++ = (color & 0x1f) << 3;
			*bmp++ = ((color >> 5) & 0x1f) << 3;
			*bmp++ = ((color >> 10) & 0x1f) << 3;
		    }
		    bmp += (64 - sizeX) * 3;
		}
	    }
	    ChangeBMP();

	    wxString s;
	    s.Printf(wxT("%d,%d"), a1 & 511, a0 & 255);
	    pos->SetLabel(s);
	    s.Printf(wxT("%d"), (a0 >> 10) & 3);
	    mode->SetLabel(s);
	    colors->SetLabel(a0 & 8192 ? wxT("256") : wxT("16"));
	    s.Printf(wxT("%d"), (a2 >> 12) & 15);
	    pallab->SetLabel(s);
	    s.Printf(wxT("%d"), a2 & 1023);
	    tile->SetLabel(s);
	    s.Printf(wxT("%d"), (a2 >> 10) & 3);
	    prio->SetLabel(s);
	    s.Printf(wxT("%dx%d"), sizeX, sizeY);
	    size->SetLabel(s);
	    if(a0 & 512) {
		s.Printf(wxT("%d"), (a1 >> 9) & 31);
		rot->SetLabel(s);
	    } else
		rot->SetLabel(wxEmptyString);
	    s = wxEmptyString;
	    if(a0 & 512)
		s.append(wxT("R--"));
	    else {
		s.append(wxT('-'));
		s.append(a1 & 4096 ? wxT('H') : wxT('-'));
		s.append(a1 & 8192 ? wxT('V') : wxT('-'));
	    }
	    s.append(a0 & 4096 ? wxT('M') : wxT('-'));
	    s.append(a0 & 1024 ? wxT('D') : wxT('-'));
	    flg->SetLabel(s);
	}
    protected:
	int sprite;
	wxControl *pos, *mode, *colors, *pallab, *tile, *prio, *size, *rot, *flg;
    };

    class GBOAMViewer : public GfxViewer
    {
    public:
	GBOAMViewer() : GfxViewer(wxT("GBOAMViewer"), 8, 16)
	{
	    sprite = 0;
	    getspin(,"Sprite", sprite);
	    getlab(pos, "Pos", "2WW,2WW");
	    getlab(tilelab, "Tile", "2WW");
	    getlab(prio, "Priority", "W");
	    getlab(oap, "OAP", "W");
	    getlab(pallab, "Palette", "W");
	    getlab(flg, "Flags", "HV");
	    getlab(banklab, "Bank", "W");
	    Fit();
	    Update();
	}
	void Update()
	{
	    u8 *bmp = image.GetData();

	    // following is almost verbatim from GBOamView.cpp
	    u16 addr = sprite * 4 + 0xfe00;

	    int size = register_LCDC & 4;

	    u8 y = gbMemory[addr++];
	    u8 x = gbMemory[addr++];
	    u8 tile = gbMemory[addr++];
	    if(size)
		tile &= 254;
	    u8 flags = gbMemory[addr++];

	    int w = 8;
	    int h = size ? 16 : 8;
	    BMPSize(w, h);

	    u8 * bank0;
	    u8 * bank1;
	    if(gbCgbMode) {
		if(register_VBK & 1) {
		    bank0 = &gbVram[0x0000];
		    bank1 = &gbVram[0x2000];
		} else {
		    bank0 = &gbVram[0x0000];
		    bank1 = &gbVram[0x2000];
		}
	    } else {
		bank0 = &gbMemory[0x8000];
		bank1 = NULL;
	    }

	    int init = 0x0000;

	    u8 *pal = gbObp0;

	    if((flags & 0x10))
		pal = gbObp1;

	    for(int yy = 0; yy < h; yy++) {
		int address = init + tile * 16 + 2*yy;
		int a = 0;
		int b = 0;

		if(gbCgbMode && flags & 0x08) {
		    a = bank1[address++];
		    b = bank1[address++];
		} else {
		    a = bank0[address++];
		    b = bank0[address++];
		}

		for(int xx = 0; xx < 8; xx++) {
		    u8 mask = 1 << (7-xx);
		    u8 c = 0;
		    if( (a & mask))
			c++;
		    if( (b & mask))
			c+=2;

		    // make sure that sprites will work even in CGB mode
		    if(gbCgbMode) {
			c = c + (flags & 0x07)*4 + 32;
		    } else {
			c = pal[c];
		    }

		    u16 color = gbPalette[c];
		    *bmp++ = (color & 0x1f) << 3;
		    *bmp++ = ((color >> 5) & 0x1f) << 3;
		    *bmp++ = ((color >> 10) & 0x1f) << 3;
		}
	    }
	    ChangeBMP();

	    wxString s;
	    s.Printf(wxT("%d,%d"), x, y);
	    pos->SetLabel(s);
	    s.Printf(wxT("%d"), tile);
	    tilelab->SetLabel(s);
	    prio->SetLabel(flags & 0x80 ? wxT("1") : wxT("0"));
	    oap->SetLabel(flags & 0x08 ? wxT("1") : wxT("0"));
	    s.Printf(wxT("%d"), flags & 7);
	    pallab->SetLabel(s);
	    s = flags & 0x20 ? wxT('H') : wxT('-');
	    s.append(flags & 0x40 ? wxT('V') : wxT('-'));
	    flg->SetLabel(s);
	    banklab->SetLabel(flags & 0x10 ? wxT("1") : wxT("0"));
	}
    protected:
	int sprite;
	wxControl *pos, *tilelab, *prio, *oap, *pallab, *flg, *banklab;
    };
}

void MainFrame::OAMViewer()
{
    switch(panel->game_type()) {
    case IMAGE_GBA:
	LoadXRCViewer(OAM);
	break;
    case IMAGE_GB:
	LoadXRCViewer(GBOAM);
	break;
    }
}

namespace Viewers {
    static int ptype = 0;
    static wxString pdir;
    void savepal(wxWindow *parent, const u8 *data, int ncols, const wxChar *type)
    {
	// no attempt is made here to translate the palette type name
	// it's just a suggested name, anyway
	wxString def_name = wxGetApp().frame->GetPanel()->game_name() +
	                    wxT('-') + type;
	if(ptype == 2)
	    def_name += wxT(".act");
	else
	    def_name += wxT(".pal");
	wxFileDialog dlg(parent, _("Select output file and type"), pdir, def_name,
			 _("Windows Palette (*.pal)|*.pal|PaintShop Palette (*.pal)|*.pal|Adobe Color Table (*.act)|*.act"),
			 wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
	dlg.SetFilterIndex(ptype);
	int ret = dlg.ShowModal();
	ptype = dlg.GetFilterIndex();
	pdir = dlg.GetDirectory();
	if(ret != wxID_OK)
	    return;
	wxFFile f(dlg.GetPath(), wxT("wb"));
	// FIXME: check for errors
	switch(ptype) {
	case 0: // Windows palette
	    {
		f.Write("RIFF", 4);
		u32 d = wxUINT32_SWAP_ON_BE(256 * 4 + 16);
		f.Write(&d, 4);
		f.Write("PAL data", 8);
		d = wxUINT32_SWAP_ON_BE(256 * 4 + 4);
		f.Write(&d, 4);
		u16 w = wxUINT16_SWAP_ON_BE(0x0300);
		f.Write(&w, 2);
		w = wxUINT16_SWAP_ON_BE(256); // cuases problems if not 16 or 256
		f.Write(&w, 2);
		for(int i = 0; i < ncols; i++, data += 3) {
		    f.Write(data, 3);
		    u8 z = 0;
		    f.Write(&z, 1);
		}
		for(int i = ncols; i < 256; i++) {
		    d = 0;
		    f.Write(&d, 4);
		}
	    }
	    break;
	case 1: // PaintShop palette
	    {
#define jasc_head "JASC-PAL\r\n0100\r\n256\r\n"
		f.Write(jasc_head, sizeof(jasc_head) - 1);
		for(int i = 0; i < ncols; i++, data += 3) {
		    char buf[14];
		    int l = sprintf(buf, "%d %d %d\r\n", data[0], data[1], data[2]);
		    f.Write(buf, l);
		}
		for(int i = ncols; i < 256; i++)
		    f.Write("0 0 0\r\n", 7);
		break;
	    }
	case 2: // Adobe color table
	    {
		f.Write(data, ncols * 3);
		u32 d = 0;
		for(int i = ncols; i < 256; i++)
		    f.Write(&d, 3);
	    }
	    break;
	}
	f.Close(); // FIXME: check for errors
    }

    class PaletteViewer : public Viewer
    {
    public:
	PaletteViewer() : Viewer(wxT("PaletteViewer"))
	{
	    colorctrl(cv, "Color");
	    pixview(bpv, "Background", 16, 16, cv);
	    pixview(spv, "Sprite", 16, 16, cv);
	    getlab(addr, "Address", "0x5000WWW");
	    getlab(val, "Value", "0xWWWW");
	    Fit();
	    Update();
	}
	void Update()
	{
	    if(paletteRAM) {
		u16 *pp = (u16 *)paletteRAM;
		u8 *bmp = colbmp;
		for(int i = 0; i < 512; i++, pp++) {
		    *bmp++ = (*pp & 0x1f) << 3;
		    *bmp++ = (*pp & 0x3e0) >> 2;
		    *bmp++ = (*pp & 0x7c00) >> 7;
		}
	    } else
		memset(colbmp, 0, sizeof(colbmp));
	    bpv->SetData(colbmp, 16, 0, 0);
	    spv->SetData(colbmp + 16*16*3, 16, 0, 0);
	    ShowSel();
	}
	void SelBG(wxMouseEvent &ev)
	{
	    spv->SetSel(-1, -1, false);
	    ShowSel();
	}
	void SelSprite(wxMouseEvent &ev)
	{
	    bpv->SetSel(-1, -1, false);
	    ShowSel();
	}
	void ShowSel()
	{
	    int x, y;
	    bool isbg = true;
	    bpv->GetSel(x, y);
	    if(x < 0) {
		isbg = false;
		spv->GetSel(x, y);
		if(x < 0) {
		    addr->SetLabel(wxEmptyString);
		    val->SetLabel(wxEmptyString);
		    return;
		}
	    }
	    int off = x + y * 16;
	    if(!isbg)
		off += 16 * 16;
	    u8 *pix = &colbmp[off * 3];
	    u16 v = (pix[0] >> 3) + ((pix[1] >> 3) << 5) + ((pix[2] >> 3) << 10);
	    wxString s;
	    s.Printf(wxT("0x%04X"), (int)v);
	    val->SetLabel(s);
	    s.Printf(wxT("0x%08X"), 0x5000000 + 2 * off);
	    addr->SetLabel(s);
	}
	void SaveBG(wxCommandEvent &ev)
	{
	    savepal(this, colbmp, 16 * 16, wxT("bg"));
	}
	void SaveOBJ(wxCommandEvent &ev)
	{
	    savepal(this, colbmp + 16 * 16 * 3, 16 * 16, wxT("obj"));
	}
	void ChangeBackdrop(wxCommandEvent &ev)
	{
	    // FIXME: this should really be a preference
	    // should also have some way of indicating selection
	    // perhaps replace w/ checkbox + colorpickerctrl
	    static wxColourData *cd = NULL;
	    wxColourDialog dlg(this, cd);
	    if(dlg.ShowModal() == wxID_OK) {
		if(!cd)
		    cd = new wxColourData();
		*cd = dlg.GetColourData();
		wxColour c = cd->GetColour();
		customBackdropColor =
		    (c.Red() >> 3) +
		    (c.Green() >> 3) << 5 +
		    (c.Blue() >> 3) << 10;
	    } else
		// kind of an unintuitive way to turn it off...
		customBackdropColor = -1;
	}
    protected:
	ColorView *cv;
	PixView *bpv, *spv;
	u8 colbmp[16*16*3*2];
	wxControl *addr, *val;

	DECLARE_EVENT_TABLE()
    };

    BEGIN_EVENT_TABLE(PaletteViewer, Viewer)
	EVT_BUTTON(XRCID("SaveBG"), PaletteViewer::SaveBG)
	EVT_BUTTON(XRCID("SaveOBJ"), PaletteViewer::SaveOBJ)
	EVT_BUTTON(XRCID("ChangeBackdrop"), PaletteViewer::ChangeBackdrop)
	EVT_GFX_CLICK(XRCID("Background"), PaletteViewer::SelBG)
	EVT_GFX_CLICK(XRCID("Sprite"), PaletteViewer::SelSprite)
    END_EVENT_TABLE()

    class GBPaletteViewer : public Viewer
    {
    public:
	GBPaletteViewer() : Viewer(wxT("GBPaletteViewer"))
	{
	    colorctrl(cv, "Color");
	    pixview(bpv, "Background", 4, 8, cv);
	    pixview(spv, "Sprite", 4, 8, cv);
	    getlab(idx, "Index", "3W");
	    getlab(val, "Value", "0xWWWW");
	    Fit();
	    Update();
	}
	void Update()
	{
	    u16 *pp = gbPalette;
	    u8 *bmp = colbmp;
	    for(int i = 0; i < 64; i++, pp++) {
		*bmp++ = (*pp & 0x1f) << 3;
		*bmp++ = (*pp & 0x3e0) >> 2;
		*bmp++ = (*pp & 0x7c00) >> 7;
	    }
	    bpv->SetData(colbmp, 4, 0, 0);
	    spv->SetData(colbmp + 4*8*3, 4, 0, 0);
	    ShowSel();
	}
	void SelBG(wxMouseEvent &ev)
	{
	    spv->SetSel(-1, -1, false);
	    ShowSel();
	}
	void SelSprite(wxMouseEvent &ev)
	{
	    bpv->SetSel(-1, -1, false);
	    ShowSel();
	}
	void ShowSel()
	{
	    int x, y;
	    bool isbg = true;
	    bpv->GetSel(x, y);
	    if(x < 0) {
		isbg = false;
		spv->GetSel(x, y);
		if(x < 0) {
		    idx->SetLabel(wxEmptyString);
		    val->SetLabel(wxEmptyString);
		    return;
		}
	    }
	    u8 *pix = &colbmp[(x + y * 4) * 3];
	    if(isbg)
		pix += 4 * 8 * 3;
	    u16 v = (pix[0] >> 3) + ((pix[1] >> 3) << 5) + ((pix[2] >> 3) << 10);
	    wxString s;
	    s.Printf(wxT("0x%04X"), (int)v);
	    val->SetLabel(s);
	    s.Printf(wxT("%d"), x + y * 4);
	    idx->SetLabel(s);
	}
	void SaveBG(wxCommandEvent &ev)
	{
	    savepal(this, colbmp, 4 * 8, wxT("bg"));
	}
	void SaveOBJ(wxCommandEvent &ev)
	{
	    savepal(this, colbmp + 4 * 8 * 3, 4 * 8, wxT("obj"));
	}
    protected:
	ColorView *cv;
	PixView *bpv, *spv;
	u8 colbmp[4*8*3*2];
	wxControl *idx, *val;
	DECLARE_EVENT_TABLE()
    };

    BEGIN_EVENT_TABLE(GBPaletteViewer, Viewer)
	EVT_BUTTON(XRCID("SaveBG"), GBPaletteViewer::SaveBG)
	EVT_BUTTON(XRCID("SaveOBJ"), GBPaletteViewer::SaveOBJ)
	EVT_GFX_CLICK(XRCID("Background"), GBPaletteViewer::SelBG)
	EVT_GFX_CLICK(XRCID("Sprite"), GBPaletteViewer::SelSprite)
    END_EVENT_TABLE()
}

void MainFrame::PaletteViewer()
{
    switch(panel->game_type()) {
    case IMAGE_GBA:
	LoadXRCViewer(Palette);
	break;
    case IMAGE_GB:
	LoadXRCViewer(GBPalette);
	break;
    }
}

namespace Viewers {
    class TileViewer : public GfxViewer
    {
    public:
	TileViewer() : GfxViewer(wxT("TileViewer"), 32*8, 32*8)
	{
	    is256 = charbase = 0;
	    getradio(,"Color16", is256, 0);
	    getradio(,"Color256", is256, 1);
	    getradio(,"CharBase0", charbase, 0);
	    getradio(,"CharBase1", charbase, 0x4000);
	    getradio(,"CharBase2", charbase, 0x8000);
	    getradio(,"CharBase3", charbase, 0xc000);
	    getradio(,"CharBase4", charbase, 0x10000);
	    getslider(,"Palette", palette);
	    getlab(tileno, "Tile", "1WWW");
	    getlab(addr, "Address", "06WWWWWW");
	    selx = sely = -1;
	    Fit();
	    Update();
	}
	void Update()
	{
	    // Following copied almost verbatim from TileView.cpp
	    u16 *palette = (u16 *)paletteRAM;
	    u8 *charBase = &vram[charbase];

	    int maxY;

	    if(is256) {
		int tile = 0;
		maxY = 16;
		for(int y = 0; y < maxY; y++) {
		    for(int x = 0; x < 32; x++) {
			if(charbase == 4 * 0x4000)
			    render256(tile, x, y, charBase, &palette[256]);
			else
			    render256(tile, x, y, charBase, palette);
			tile++;
		    }
		}
		BMPSize(32*8, maxY*8);
	    } else {
		int tile = 0;
		maxY = 32;
		if(charbase == 3 * 0x4000)
		    maxY = 16;
		for(int y = 0; y < maxY; y++) {
		    for(int x = 0; x < 32; x++) {
			render16(tile, x, y, charBase, palette);
			tile++;
		    }
		}
		BMPSize(32*8, maxY*8);
	    }
	    ChangeBMP();
	    UpdateMouseInfo();
	}
	void UpdateMouseInfoEv(wxMouseEvent &ev)
	{
	    selx = ev.GetX();
	    sely = ev.GetY();
	    UpdateMouseInfo();
	}

	void UpdateMouseInfo()
	{
	    if(selx > gv->bmw || sely > gv->bmh)
		selx = sely = -1;
	    if(selx < 0) {
		addr->SetLabel(wxEmptyString);
		tileno->SetLabel(wxEmptyString);
	    } else {
		int x = selx / 8;
		int y = sely / 8;
		int t = 32 * y + x;
		if(is256)
		    t *= 2;
		wxString s;
		s.Printf(wxT("%d"), t);
		tileno->SetLabel(s);
		s.Printf(wxT("%08X"), 0x6000000 + charbase + 32 * t);
		addr->SetLabel(s);
	    }
	}
	// following 2 functions copied almost verbatim from TileView.cpp
	void render256(int tile, int x, int y, u8 *charBase, u16 *palette)
	{
	    u8 *bmp = &image.GetData()[24*x + 8*32*24*y];

	    for(int j = 0; j < 8; j++) {
		for(int i = 0; i < 8; i++) {
		    u8 c = charBase[tile*64 + j * 8 + i];

		    u16 color = palette[c];

		    *bmp++ = (color & 0x1f) << 3;
		    *bmp++ = ((color >> 5) & 0x1f) << 3;
		    *bmp++ = ((color >> 10) & 0x1f) << 3;

		}
		bmp += 31*24; // advance line
	    }
	}

	void render16(int tile, int x, int y, u8 *charBase, u16 *palette)
	{
	    u8 *bmp = &image.GetData()[24*x + 8*32*24*y];

	    int pal = this->palette;

	    if(this->charbase == 4 * 0x4000)
		pal += 16;

	    for(int j = 0; j < 8; j++) {
		for(int i = 0; i < 8; i++) {
		    u8 c = charBase[tile*32 + j * 4 + (i>>1)];

		    if(i & 1)
			c = c>>4;
		    else
			c = c & 15;

		    u16 color = palette[pal*16+c];

		    *bmp++ = (color & 0x1f) << 3;
		    *bmp++ = ((color >> 5) & 0x1f) << 3;
		    *bmp++ = ((color >> 10) & 0x1f) << 3;
		}
		bmp += 31*24; // advance line
	    }
	}

    protected:
	int charbase, is256, palette;
	wxControl *tileno, *addr;
	int selx, sely;

	DECLARE_EVENT_TABLE()
    };

    BEGIN_EVENT_TABLE(TileViewer, GfxViewer)
	EVT_GFX_CLICK(wxID_ANY, TileViewer::UpdateMouseInfoEv)
    END_EVENT_TABLE()

    class GBTileViewer : public GfxViewer
    {
    public:
	GBTileViewer() : GfxViewer(wxT("GBTileViewer"), 16*8, 16*8)
	{
	    bank = charbase = 0;
	    getradio(,"Bank0", bank, 0);
	    getradio(,"Bank1", bank, 0x2000);
	    getradio(,"CharBase0", charbase, 0);
	    getradio(,"CharBase1", charbase, 0x800);
	    getslider(,"Palette", palette);
	    getlab(tileno, "Tile", "2WW");
	    getlab(addr, "Address", "WWWW");
	    selx = sely = -1;
	    Fit();
	    Update();
	}
	void Update()
	{
	    // following copied almost verbatim from GBTileView.cpp
	    u8 *charBase = (gbVram != NULL) ?
		&gbVram[bank+charbase] :
		&gbMemory[0x8000+charbase];

	    int tile = 0;
	    for(int y = 0; y < 16; y++) {
		for(int x = 0; x < 16; x++) {
		    render(tile, x, y, charBase);
		    tile++;
		}
	    }
	    ChangeBMP();
	    UpdateMouseInfo();
	}
	void UpdateMouseInfoEv(wxMouseEvent &ev)
	{
	    selx = ev.GetX();
	    sely = ev.GetY();
	    UpdateMouseInfo();
	}

	void UpdateMouseInfo()
	{
	    if(selx > gv->bmw || sely > gv->bmh)
		selx = sely = -1;
	    if(selx < 0) {
		addr->SetLabel(wxEmptyString);
		tileno->SetLabel(wxEmptyString);
	    } else {
		int x = selx / 8;
		int y = sely / 8;
		int t = 16 * y + x;
		wxString s;
		s.Printf(wxT("%d"), t);
		tileno->SetLabel(s);
		s.Printf(wxT("%04X"), 0x8000 + charbase + 16 * t);
		addr->SetLabel(s);
	    }
	}

	// following function copied almost verbatim from GBTileView.cpp
	void render(int tile, int x, int y, u8 *charBase)
	{
	    u8 *bmp = &image.GetData()[24*x + 8*16*24*y];

	    for(int j = 0; j < 8; j++) {
		u8 mask = 0x80;
		u8 tile_a = charBase[tile*16+j*2];
		u8 tile_b = charBase[tile*16+j*2+1];

		for(int i = 0; i < 8; i++) {
		    u8 c = (tile_a & mask) ? 1 : 0;
		    c += ((tile_b & mask) ? 2 : 0);

		    if(gbCgbMode) {
			c = c + palette*4;
		    } else {
			c = gbBgp[c];
		    }

		    u16 color = gbPalette[c];

		    *bmp++ = (color & 0x1f) << 3;
		    *bmp++ = ((color >> 5) & 0x1f) << 3;
		    *bmp++ = ((color >> 10) & 0x1f) << 3;

		    mask >>= 1;
		}
		bmp += 15*24; // advance line
	    }
	}
    protected:
	int bank, charbase, palette;
	wxControl *addr, *tileno;
	int selx, sely;

	DECLARE_EVENT_TABLE()
    };

    BEGIN_EVENT_TABLE(GBTileViewer, GfxViewer)
	EVT_GFX_CLICK(wxID_ANY, GBTileViewer::UpdateMouseInfoEv)
    END_EVENT_TABLE()
}

void MainFrame::TileViewer()
{
    switch(panel->game_type()) {
    case IMAGE_GBA:
	LoadXRCViewer(Tile);
	break;
    case IMAGE_GB:
	LoadXRCViewer(GBTile);
	break;
    }
}
